using System;
using System.Collections.Generic;
using System.Threading;
using JK.EventRouter.Eventing;
using JK.EventRouter.Threading;
using JK.EventRouter.WorkItems;

namespace JK.EventRouter
{
    /// <summary>delegate that allows void return type and argument-less signature</summary>
    public delegate void Act();

    /// <summary>delegate that allows void return type and Exception argument</summary>
    public delegate void ExceptionEventHandler(Exception e);

    /// <summary>A mediator pattern implementation used to decouple GUI controls.  It also provides centralized exception handling for all
    /// work items processed, and asynchronous processing of work through a single threaded blocking queue.
    /// </summary>
    public sealed class EventRouter : IEventRouter
    {
        private Act _beforeWork;
        private Act _afterWork;
        private ExceptionEventHandler _onException;
        private readonly Dictionary<string, SubscriberMap> _workItemMap = new Dictionary<string, SubscriberMap>();
        /// <summary>Promote thread safety by enqueing work onto a queue that is processed on a background thread</summary>
        private readonly BlockingQueue<Act> _queue = new BlockingQueue<Act>();
        private readonly Thread _backgroundThread;

        #region Constructors
        /// <summary>default ctor</summary>
        public EventRouter() : this(delegate { }, delegate { }, e => Console.WriteLine(e)){}
       
        /// <summary>Construct the eventRouter with appropriate behaviors
        /// <param name="afterWork">Delegate to invoke after each work item is complete</param>
        /// <param name="beforeWork">Delegate to invoke before each work item is started</param>
        /// <param name="onException">Delegate to invoke to process exceptions that occur during work item processing</param>
        /// </summary>
        public EventRouter(Act beforeWork, Act afterWork, ExceptionEventHandler onException)
        {
        
            _beforeWork = beforeWork;
            _afterWork = afterWork;
            _onException = onException;
            
            _backgroundThread = new Thread(DequeueWork);
            _backgroundThread.Name = "EventRouter";
            _backgroundThread.IsBackground = true;
            _backgroundThread.Start();
        }

        #endregion

        public IEventRouter SetBeforeWorkDelegate(Act beforeWork)
        {
            _beforeWork = beforeWork;
            return this;
        }

        public IEventRouter SetAfterWorkDelegate(Act afterWork)
        {
            _afterWork = afterWork;
            return this;
        }

        public IEventRouter SetOnExceptionDelegate(ExceptionEventHandler onException)
        {
            _onException = onException;
            return this;
        }

        /// <summary>Subscribe an instance of <see cref="IAction{T}"/> where T is an implementation of IWorkItem.  
        /// Depending on the implementation of IAction, work may be processed on a background thread <see cref="GuiThreadAction{T}"/> 
        /// OR marshalled to the gui thread <see cref="BackgroundThreadAction{T}"/> 
        /// </summary>
        public void Subscribe<T>(object subscriber, IAction<T> action) where T : IWorkItem
        {
            string key = GetKey(typeof(T));
            if (!_workItemMap.ContainsKey(key))
            {
                SubscriberMap subscribers =  new SubscriberMap();
                subscribers.Add(subscriber, action);
                _workItemMap.Add(key, subscribers);
            }
            else
            {
                SubscriberMap subscribers = (SubscriberMap)_workItemMap[key];
                subscribers.Add(subscriber, action);
            }
        }

        /// <summary>Unsubscribe a subscriber from the specified IWorkItem type</summary>
        public void Unsubscribe<T>(object subscriber) where T : IWorkItem
        {
            string key = GetKey(typeof (T));
            if (_workItemMap.ContainsKey(key))
            {
                SubscriberMap subscribers = _workItemMap[key];
                subscribers.Remove(subscriber);
            }
        }

        /// <summary>Publish a IWorkItem for consumption by one or more subscribers</summary>
        public void Publish<T>(T item) where T : IWorkItem
        {
           
            //NOTE : Careful!  The key will be the concrete type of the item,
            //however when we iterate over the subscriber actions, we try to cast using T.
            //If T is not the same concrete type that the subscriber used for the action, 
            //a cast exception will occur. 
            //
            //
            //Example - 
            // : Subscribe with a GuiThreadAction<ModifyProductOfferDecisionWorkItem> action
            // _eventRouter.Subscribe(this, new GuiThreadAction<ModifyProductOfferDecisionWorkItem>(OnConfigureExistingDecision, owner));
            //
            // : Instantiate a ModifyProductOfferDecisionWorkItem instance, but refer to it by the IWorkItem interface
            //IWorkItem workItem = new ModifyProductOfferDecisionWorkItem()
            //
            // : Publish the workItem.  It will treat T as IWorkItem rather than the concrete
            // : ModifyProductOfferDecisionWorkItem type. This will cause an exception since the subscriber
            // : registered a GuiThreadAction<ModifyProductOfferDecisionWorkItem> callback
            //
            //.Publish(workItem); Exception!

            //subscriptions are keyed by the full name of the item argument type
            string key = item.GetType().FullName;
            if (_workItemMap.ContainsKey(key))
            {
                SubscriberMap subscribers = (SubscriberMap) _workItemMap[key];

                foreach (IAction<T> action in subscribers.GetActions<T>())
                {
                    QueueWorkItem(item, action);
                }
            }
            else
            {
                //"No subscriber exists for {0}", key);
            }

         
        }

        /// <summary>Execute a simple action on the eventRouter</summary>
        public void Execute(IAction action)
        {
            _queue.Enqueue(action.Act);
        }

        /// <summary>Execute an action, invoking the callback when the action is completed</summary>
        public void Execute(IAction action, IAction callback) 
        {
            _queue.Enqueue(new Act(
                               delegate
                                   {
                                       action.Act();
                                       callback.Act();
                                   }));
        }

        /// <summary>Enqueue work onto the blocking queue to promote thread safety</summary>
        private void QueueWorkItem<T>(T item, IAction<T> action) where T : IWorkItem
        {
           
            _queue.Enqueue(new Act(
                               delegate
                                   {
                                       try
                                       {
                                           action.Act(item);
                                       }
                                       finally
                                       {
                                           if (item is ICompletableWorkItem)
                                           {
                                               ICompletableWorkItem completable = ((ICompletableWorkItem)item);
                                               if(completable.CanComplete){completable.OnComplete();}
                                           }
                                       }
                                   }));

        }

        /// <summary>Executed on the background thread to process queued work</summary>
        private void DequeueWork()
        {
            try
            {
                while (true)
                {
                    Act action = _queue.Dequeue();

                    try
                    {
                        _beforeWork();
                        action.Invoke();
                        _afterWork();
                    }
                    catch (ThreadInterruptedException)
                    {
                        _afterWork();
                        return;
                    }
                    catch (ThreadAbortException)
                    {
                        _afterWork();
                        return;
                    }
                    catch(CrossThreadException cte)
                    {
                        _afterWork();
                        _onException(cte.OriginalException);
                    }
                    catch (Exception e)
                    {
                        _afterWork();
                        _onException(e);
                    }
                }
            }
            catch (ThreadInterruptedException) 
            {
                return;
            }
            catch (ThreadAbortException)
            {
                return;
            }
            catch (Exception exception)
            {
                //TODO : need logging
                Console.WriteLine(exception);
            }
        }

        public void Dispose()
        {
            _backgroundThread.Interrupt();
            foreach (KeyValuePair<string, SubscriberMap> pair in _workItemMap)
            {
                pair.Value.Dispose();
            }
            _workItemMap.Clear();
        }

        private string GetKey(Type type)
        {
            //subscriptions are keyed by the full name of type T
            return type.FullName;
        }
    }
}